package com.peacebird.utils.excel;

import com.peacebird.utils.annotation.ExcelFieldName;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author: ZYZ
 * @DateTime: 2020/2/28 10:32
 * @Description:
 */
public class ExcelReader<T> {
    // 日志打印类
    private static Logger logger = LoggerFactory.getLogger(ExcelReader.class);
    private static final String XLS = "xls";
    private static final String XLSX = "xlsx";


    private static final String SET_PREFIX = "set";
    private static final String GET_PREFIX = "get";
    /**
     * 根据文件后缀名类型获取对应的工作簿对象
     * @param inputStream 读取文件的输入流
     * @param fileType 文件后缀名类型（xls或xlsx）
     * @return 包含文件数据的工作簿对象
     * @throws IOException
     */
    public static Workbook getWorkbook(InputStream inputStream, String fileType) throws IOException {
        Workbook workbook = null;
        if (fileType.equalsIgnoreCase(XLS)) {
            workbook = new HSSFWorkbook(inputStream);
        } else if (fileType.equalsIgnoreCase(XLSX)) {
            workbook = new XSSFWorkbook(inputStream);
        }
        return workbook;
    }

    /**
     * 读取Excel文件内容
     * @param fileName 要读取的Excel文件所在路径
     * @return 读取结果列表，读取失败时返回null
     */
    public static<T> List<T> readExcel(String fileName, Integer ignoreRowCount, Class <T> entityClass) {

        Workbook workbook = null;
        FileInputStream inputStream = null;

        try {
            // 获取Excel后缀名
            String fileType = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
            // 获取Excel文件
            File excelFile = new File(fileName);
            if (!excelFile.exists()) {
                logger.error("指定的Excel文件不存在！");
                //XxlJobLogger.log("指定的Excel文件不存在:{}",fileName);
                return null;
            }

            // 获取Excel工作簿
            inputStream = new FileInputStream(excelFile);
            workbook = getWorkbook(inputStream, fileType);

            // 读取excel中的数据
            List<T> resultDataList = parseExcel(workbook,ignoreRowCount, entityClass);

            return resultDataList;
        } catch (Exception e) {
            logger.error("解析Excel失败，文件名：" + fileName + " 错误信息：" + e.getMessage());
            //XxlJobLogger.log("解析Excel失败，文件名： {} 错误信息：{}" ,fileName,e.getMessage());
            return null;
        } finally {
            try {
                if (null != workbook) {
                    workbook.close();
                }
                if (null != inputStream) {
                    inputStream.close();
                }
            } catch (Exception e) {
                logger.error("关闭数据流出错！错误信息：" + e.getMessage());
                //XxlJobLogger.log("关闭数据流出错！错误信息：{}" ,e.getMessage());
                return null;
            }
        }
    }

    /**
     * 解析Excel数据
     * @param workbook Excel工作簿对象
     * @return 解析结果
     */
    private static<T> List<T> parseExcel(Workbook workbook, Integer ignoreRowCount,Class<T> clazz) {
        List<T> resultDataList = new ArrayList<>();
        // 解析sheet
        for (int sheetNum = 0; sheetNum < workbook.getNumberOfSheets(); sheetNum++) {
            Sheet sheet = workbook.getSheetAt(sheetNum);

            // 校验sheet是否合法
            if (sheet == null) {
                continue;
            }

            // 获取第一行数据(表头)
            int firstRowNum = sheet.getFirstRowNum() + ignoreRowCount;
            Row firstRow = sheet.getRow(firstRowNum);
            if (null == firstRow) {
                //XxlJobLogger.log("解析Excel表头失败，在第{}行没有读取到任何数据！",firstRowNum);
                break;
            }


            // 解析每一行的数据，构造数据对象
            int rowStart = firstRowNum + 1;
            //int rowEnd = sheet.getPhysicalNumberOfRows(); //物理行数，也就是不包括那些空行（隔行）的情况
            int rowEnd = sheet.getLastRowNum();

            for (int rowNum = rowStart; rowNum <= rowEnd; rowNum++) {
                Row row = sheet.getRow(rowNum);

                if (null == row) {
                    continue;
                }

                T resultData = convertRowToData(row,firstRow,clazz);
                if (null == resultData) {
                    //XxlJobLogger.log("第{}行数据不合法，已忽略！",row.getRowNum());
                    continue;
                }
                resultDataList.add(resultData);
            }
        }

        return resultDataList;
    }


    /**
     * 将单元格内容转换为字符串
     * @param cell
     * @return
     */
    private static String convertCellValueToString(Cell cell) {
        if(cell==null){
            return null;
        }
        String returnValue = null;
        switch (cell.getCellType()) {
            //数字
            case NUMERIC:
                Double doubleValue = cell.getNumericCellValue();

                // 格式化科学计数法，取一位整数
                DecimalFormat df = new DecimalFormat("0");
                returnValue = df.format(doubleValue);
                break;
            //字符串
            case STRING:
                returnValue = cell.getStringCellValue();
                break;
            //布尔
            case BOOLEAN:
                Boolean booleanValue = cell.getBooleanCellValue();
                returnValue = booleanValue.toString();
                break;
            // 空值
            case BLANK:
                break;
            // 公式
            case FORMULA:
                returnValue = cell.getCellFormula();
                break;
            // 故障
            case ERROR:
                break;
            default:
                break;
        }
        return returnValue;
    }

    /**
     * 提取每一行中需要的数据，构造成为一个结果数据对象
     *
     * 当该行中有单元格的数据为空或不合法时，忽略该行的数据
     *
     * @param row 行数据
     * @return 解析后的行数据对象，行数据错误时返回null
     */
    private static<T> T convertRowToData(Row row, Row headRow,Class<T> clazz) {
        T obj = null;

        try {
            obj = clazz.newInstance();
            String headName ="";
            String value = "";
            Cell headCell = null;
            Cell dataCell = null;
            for (int i = 0; i < headRow.getLastCellNum(); i++) {
                headCell = headRow.getCell(i);
                if (null == headCell) {
                    continue;
                }
                dataCell = row.getCell(i);

                value = convertCellValueToString(dataCell).trim();
                headName = headCell.getStringCellValue().trim();
                if (headName == null) {
                    continue;
                }

                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    String annotation = field.getAnnotation(ExcelFieldName.class).value();
                    if (headName.equalsIgnoreCase(annotation)) {
                        String methodName ="";
                        if(null == field.getName() || field.getName().length() == 0){
                            methodName = SET_PREFIX + field.getName();

                        }else{
                            methodName = SET_PREFIX + (field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1));
                        }
                        Method method = clazz.getMethod(methodName, field.getType());
                        method.invoke(obj, value);
                    }
                }



            }


        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }


        return obj;
    }
}
